package com.wm.blog_search.service;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.wm.blog_common.base.BeanUtils;
import com.wm.blog_common.bean.PageHelper;
import com.wm.blog_common.constatnt.CommonConstant;
import com.wm.blog_config.Document.BlogDocument;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.QueryStringQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.SearchQuery;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;

/**
 * elasticsearch 搜索引擎
 *
 * @author 半卷流年
 * @date 2020-5-22 17:38
 */
@Slf4j
@Service
public class BaseSearchServiceImpl<T> {

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    /**
     * 搜索
     *
     * @param keyword
     * @param clazz
     * @return
     */
    public List<T> query(String keyword, Class<T> clazz) {
        SearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(new QueryStringQueryBuilder(keyword))
                .withSort(SortBuilders.scoreSort().order(SortOrder.DESC))
                // .withSort(new FieldSortBuilder("createTime").order(SortOrder.DESC))
                .build();
        return elasticsearchTemplate.queryForList(searchQuery, clazz);
    }

    /**
     * 高亮显示
     */
    public List<Map<String, Object>> queryHit(String keyword, String indexName, String... fieldNames) {
        // 构造查询条件,使用标准分词器.
        QueryBuilder matchQuery = createQueryBuilder(keyword, fieldNames);

        // 设置高亮,使用默认的highlighter高亮器
        HighlightBuilder highlightBuilder = createHighlightBuilder(fieldNames);

        // 设置查询字段
        SearchResponse response = elasticsearchTemplate.getClient().prepareSearch(indexName)
                .setQuery(matchQuery)
                .highlighter(highlightBuilder)
                // 设置一次返回的文档数量，最大值：10000
                .setSize(10000)
                .get();
        // 返回搜索结果
        SearchHits hits = response.getHits();
        return getHitList(hits);
    }

    /**
     * 高亮显示，返回分页
     */
    public Page<Map<String, Object>> queryHitByPage(int pageNo, int pageSize, String keyword, String indexName, String... fieldNames) {
        // 构造查询条件,使用标准分词器.
        QueryBuilder matchQuery = createQueryBuilder(keyword, fieldNames);
        // 设置高亮,使用默认的highlighter高亮器
        HighlightBuilder highlightBuilder = createHighlightBuilder(fieldNames);
        // 设置查询字段
        SearchResponse response = elasticsearchTemplate.getClient().prepareSearch(indexName)
                .setQuery(matchQuery)
                .highlighter(highlightBuilder)
                .setFrom((pageNo - 1) * pageSize)
                // 设置一次返回的文档数量，最大值：10000
                .setSize(pageNo * pageSize)
                .get();

        // 返回搜索结果
        SearchHits hits = response.getHits();
        //侯建分页参数
        Long totalCount = hits.getTotalHits();
        IPage<Object> iPage = new Page<>(pageNo, pageSize, totalCount);
        Page<Map<String, Object>> page = PageHelper.newPage(iPage, getHitList(hits));
        return page;
    }

    /**
     * 构造查询条件
     *
     */
    private QueryBuilder createQueryBuilder(String keyword, String... fieldNames) {
        // 构造查询条件,使用标准分词器,// matchQuery(),单字段搜索
        return QueryBuilders.multiMatchQuery(keyword,fieldNames)
                .analyzer("ik_max_word")
                .operator(Operator.OR);
    }

    /**
     * 构造高亮器
     */
    private HighlightBuilder createHighlightBuilder(String... fieldNames) {
        // 设置高亮,使用默认的highlighter高亮器
        HighlightBuilder highlightBuilder = new HighlightBuilder()
                // .field("productName")
                .preTags("<span style='color:red'>")
                .postTags("</span>");
        // 设置高亮字段
        for (String fieldName : fieldNames) {
            highlightBuilder.field(fieldName);
        }
        return highlightBuilder;
    }

    /**
     * 处理高亮结果
     */
    private List<Map<String, Object>> getHitList(SearchHits hits) {
        List<Map<String, Object>> list = new ArrayList<>();
        Map<String, Object> map;
        for (SearchHit searchHit : hits) {
            map = new HashMap<>();
            // 处理源数据
            map.put("source", searchHit.getSourceAsMap());
            // 处理高亮数据
            Map<String, Object> hitMap = new HashMap<>();
            searchHit.getHighlightFields().forEach((k, v) -> {
                String hight = "";
                for (Text text : v.getFragments()) {
                    hight += text.string();
                }
                hitMap.put(v.getName(), hight);
            });
            map.put("highlight", hitMap);
            list.add(map);
        }
        return list;
    }

    /**
     * 删除索引
     * @param indexName
     */
    public void deleteIndex(String indexName) {
        elasticsearchTemplate.deleteIndex(indexName);
    }


    /**
     * 修改文档数据,局部数据更新,必须保证id存在
     * @param document
     */
    public void updateDocument(BlogDocument document) {
        //对象转成map
        Map<String, Object> map = BeanUtils.object2Map(document);

            UpdateResponse doc = null;
        try {
            doc = elasticsearchTemplate
                    .getClient()
                    .prepareUpdate(CommonConstant.BLOG_ES_INDEX_NAME, CommonConstant.BLOG_ES_TYPE, document.getId().toString())
                    .setDoc(map)
                    .execute()
                    .get();
        } catch (InterruptedException | ExecutionException e) {
            log.error("更新es文档数据异常:{}",e.getMessage(),e);
        }
        log.info("更新文档数据:{}", JSONObject.toJSONString(doc));
    }

    /**
     * 根据文档id删除文档数据
     * @param id
     */
    @Deprecated //使用blogDocumentRepository替代
    public void deleteDocument(Long id) {
        if(ObjectUtils.isEmpty(id)){
            log.info("文档Id为空");
            return;
        }
        String delete = elasticsearchTemplate.delete(CommonConstant.BLOG_ES_INDEX_NAME, CommonConstant.BLOG_ES_TYPE, id.toString());
        log.info("删除es文档id:{},{}",id,delete);
    }
}
